#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define GPIOLED_CNT    1   // 设备号数目
#define GPIOLED_NAME   "gpioled" // 名字
#define LEDOFF         0 // 关灯
#define LEDON          1 // 开灯


struct gpioled_dev{  // 设备结构体
  dev_t devid;       // 设备号
  int major;         // 主设备号
  int minor;         // 次设备号
  struct cdev cdev;  // 字符设备
  struct class* class; // 类
  struct device_node* nd; // 设备节点
  struct device* device; // 设备
  int led_gpio;  // led所使用的编号
};

struct gpioled_dev gpioled; // led设备

static int led_open(struct inode* inode, struct file* filp){
   filp->private_data = &gpioled; // 设置私有数据
   return 0;
}

static ssize_t led_read(struct file* filp, char __user* buf, size_t cnt, loff_t* offt){
   return 0;
}

static ssize_t led_write(struct file* filp, const char __user* buf, 
                        size_t cnt, loff_t* offt){
      int retvalue;
      unsigned char databuf[1];
      unsigned char ledstat;
      struct gpioled_dev* dev = filp->private_data;
      retvalue = copy_from_user(databuf, buf, cnt);
      if(retvalue < 0){
         printk("kernel write failed \r\n");
         return -1;
      }
      
      ledstat = databuf[0];

      if(ledstat == LEDON){
         gpio_set_value(dev->led_gpio, 1); // 打开LED
      }else if(ledstat == LEDOFF){
         gpio_set_value(dev->led_gpio, 0); // 关灯
      }

      return 0;
}

static int led_release(struct inode* inode, struct file* filp){
   return 0;
}

static struct file_operations gpioled_fops = { // 设备操作函数
  .owner = THIS_MODULE,
  .open = led_open,
  .read = led_read,
  .write = led_write,
  .release =  led_release,
};


static int __init led_init(void){
   int ret = 0;
   const char* str;
  /* 设置LED所使用的GPIO */
  // 1.获取设备节点
   gpioled.nd = of_find_node_by_path("/gpioled");
   if(gpioled.nd == NULL){
      printk("gpioled node not find \r\n");
      return -1;
   }
   // 2.读取status属性
   ret = of_property_read_string(gpioled.nd, "status", &str);
   if(ret < 0){
      printk("read status error \r\n");
      return -1;
   }
   if(strcmp(str, "okay")){
      printk("status is not okay \r\n");
      return -1;
   }
   // 3.读取compatible属性值
   ret = of_property_read_string(gpioled.nd, "compatible", &str);
   if(ret < 0){
      printk("read compatible error \r\n");
      return -1;
   }
   if(strcmp(str, "atkrk3568-gpio-led")){
      printk("compatible is not atkrk3568-gpio-led \r\n");
      return -1;
   }
   //4.获取设备树中的gpio属性，得到LED所使用的LED编号
   gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); // 0取第一个
   if(gpioled.led_gpio < 0){
      printk("cant get led-gpio \r\n");
      return -1;
   }
   printk("led-gpio num = %d \r\n", gpioled.led_gpio);
   //5.向gpio子系统申请使用GPIO 当前驱动独自占有此驱动
   ret = gpio_request(gpioled.led_gpio, "LED-GPIO");
   if(ret){
      printk("request led-gpio failed \r\n");
      return -1;
   }
   //6.设置GPIO为输出，并且输出低电平，关闭LED灯
   ret = gpio_direction_output(gpioled.led_gpio, 0);
   if(ret < 0){
      printk("cant set gpio \r\n");
      return -1;
   }

  /* 注册字符设备驱动 */
  // 1.创建设备号
 if(gpioled.major){
    gpioled.devid = MKDEV(gpioled.major, 0);
    ret = register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME); // 注册字符设备号
    if(ret < 0){
      gpio_free(gpioled.led_gpio);
      printk("cant register %s char driver ret = %d \r\n", GPIOLED_NAME, ret);
      return -1;
    }
 }else{
    ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);  // 申请设备号
    if(ret < 0){
      gpio_free(gpioled.led_gpio);
      printk("cant alloc_chrdev_region %s, ret = %d \r\n", GPIOLED_NAME, ret);
      return -1;
    }
    gpioled.major = MAJOR(gpioled.devid);
    gpioled.minor = MINOR(gpioled.devid); // 获取主次设备号
 }
 printk("%s gpioled major = %d, minnor=%d \r\n", GPIOLED_NAME, gpioled.major, gpioled.minor);

 // 2.初始化字符驱动
 gpioled.cdev.owner = THIS_MODULE;
 cdev_init(&gpioled.cdev, &gpioled_fops);

 // 3.添加一个字符驱动
 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
 if(ret < 0){  // 添加失败
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注销掉设备号
    gpio_free(gpioled.led_gpio);
    return -1;
 }

 // 4.创建类
 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
 if(IS_ERR(gpioled.class)){ // 创建类失败
    cdev_del(&gpioled.cdev); // 删掉字符驱动
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注销掉设备号
    gpio_free(gpioled.led_gpio);
    return -1;
 }
 // 5.创建设备
 gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
 if(IS_ERR(gpioled.device)){ // 设备创建失败
    class_destroy(gpioled.class); // 删掉类
    cdev_del(&gpioled.cdev); // 删掉字符驱动
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注销掉设备号
    gpio_free(gpioled.led_gpio);
    return -1;
 }

  return 0;
}

static void __exit led_exit(void){
   device_destroy(gpioled.class, gpioled.devid); // 注销设备
   class_destroy(gpioled.class); // 删掉类
   cdev_del(&gpioled.cdev); // 删掉字符驱动
   unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); // 注销掉设备号
   gpio_free(gpioled.led_gpio);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Narnat");
